/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.cryptoutil;

import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Provider.Service;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Hex;

public class CryptoUtil {
	private CryptoUtil() {
	}
	
	public static String generateRandom(String algorithm, int length, 
			String seed, String encoding, String provider) throws GeneralSecurityException,
			UnsupportedEncodingException {
		byte[] seedBytes = null;
		if(seed != null)
			seedBytes = seed.getBytes(encoding);
		byte[] rnd = CryptoUtil.generateRandom(algorithm, length, seedBytes, provider);
		return Hex.encodeHexString(rnd);
	}
	
	public static byte[] generateRandom(String algorithm, int length, 
			byte[] seed, String provider) throws GeneralSecurityException {

		if(algorithm == null)
			throw new NoSuchAlgorithmException("Algorithm cannot null");
		SecureRandom secureRandom = null;
		if(provider == null)
			secureRandom = SecureRandom.getInstance(algorithm);
		else
			secureRandom = SecureRandom.getInstance(algorithm, provider);
		if(seed != null && seed.length > 0)
			secureRandom.setSeed(seed);
		byte[] bytes = new byte[(length/8)];
		secureRandom.nextBytes(bytes);
		return bytes;
	}
	
	//caller is responsible to close the OutputStream if necessary
	public static void listProviderService(OutputStream out) {
		PrintWriter writer = new PrintWriter(out);
		for(Provider provider : Security.getProviders()) {			
			for(Service service : provider.getServices())
				writer.println(service.toString());
		}
		writer.flush();
	}
	
	public static String digest(String message, String algorithm, 
			String encoding, String provider) throws GeneralSecurityException, 
			UnsupportedEncodingException {
		return Hex.encodeHexString(CryptoUtil.digest(message.getBytes(encoding), algorithm, provider));
	}
	
	public static byte[] digest(byte[] message, String algorithm, 
			String provider) throws GeneralSecurityException {
		MessageDigest md = null;
		if(provider == null)
			md = MessageDigest.getInstance(algorithm);
		else
			md = MessageDigest.getInstance(algorithm, provider);		
		md.update(message);
		return md.digest();
	}
	
	public static String encrypt(String clearText, String key, String iv, 
			String algorithm, String mode, String padding, String encoding, String provider) 
	throws GeneralSecurityException, UnsupportedEncodingException {
		byte[] ivByte = iv == null ? null : iv.getBytes(encoding);
		byte[] cipher = CryptoUtil.encrypt(clearText.getBytes(encoding), 
				key.getBytes(encoding), ivByte, algorithm, mode, padding, provider);
		return Hex.encodeHexString(cipher);
	}
	
	public static byte[] encrypt(byte[] clearText, byte[] key, byte[] iv, 
			String algorithm, String mode, String padding, String provider) 
	throws GeneralSecurityException {

		String transformation = algorithm;
		if(mode != null && padding != null)
			 transformation = transformation + "/" + mode + "/" + padding;
		
		AlgorithmParameterSpec algoSpec = null;
		if(iv != null)
			algoSpec = new IvParameterSpec(iv);

		SecretKeySpec keySpec = new SecretKeySpec(key, algorithm);
		Cipher cipher = null;
		if(provider == null || provider.length() < 1)
			cipher = Cipher.getInstance(transformation);
		else
			cipher = Cipher.getInstance(transformation, provider);
		if(algoSpec == null)
			cipher.init(Cipher.ENCRYPT_MODE, keySpec);
		else
			cipher.init(Cipher.ENCRYPT_MODE, keySpec, algoSpec);
		return cipher.doFinal(clearText);
	}
	
	public static String aesEncryptUtf8(String clearText, String key, String provider) 
	throws UnsupportedEncodingException, GeneralSecurityException {
		return CryptoUtil.aesEncryptUtf8(clearText, key, null, null, null, provider);
	}
	
	public static String aesEncryptUtf8(String clearText, String key, String iv, 
			String mode, String padding, String provider) 
	throws UnsupportedEncodingException, GeneralSecurityException {
		return CryptoUtil.encrypt(clearText, key, iv, "AES", mode, padding, "UTF-8", provider);
	}
}